package com.devbobcorn.nekoration.blocks;

import java.util.Map;

import com.devbobcorn.nekoration.NekoConfig;
import com.devbobcorn.nekoration.NekoConfig.HorConnectionDir;
import com.devbobcorn.nekoration.blocks.states.HorizontalConnection;
import com.devbobcorn.nekoration.blocks.states.ModStateProperties;
import com.devbobcorn.nekoration.common.VanillaCompat;

import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.ActionResult;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.Properties;
import net.minecraft.state.property.EnumProperty;
import net.minecraft.state.property.IntProperty;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.block.ShapeContext;
import net.minecraft.util.shape.VoxelShape;

public class DyeableHorizontalConnectBlock extends DyeableHorizontalBlock {
    private static final Map<Direction, VoxelShape> AABBs = getAABBs(4.0D);

    public static final IntProperty COLOR = Properties.LEVEL_15;

    public enum ConnectionType {
        DOUBLE, TRIPLE, BEAM;
    }

    public static final EnumProperty<HorizontalConnection> CONNECTION  = ModStateProperties.HONRIZONTAL_CONNECTION;

    protected void appendSettings(StateManager.Builder<Block, BlockState> s) {
        s.add(COLOR, FACING, CONNECTION);
    }

    public final ConnectionType type;
    public final boolean connectOtherVariant;

    public DyeableHorizontalConnectBlock(Settings settings) {
        super(settings);
        type = ConnectionType.TRIPLE;
        connectOtherVariant = false;
        this.setDefaultState(this.stateManager.getDefaultState().with(COLOR, 14));
    }

    public DyeableHorizontalConnectBlock(Settings settings, ConnectionType tp, boolean co) {
        super(settings);
        type = tp;
        connectOtherVariant = co;
        this.setDefaultState(this.stateManager.getDefaultState().with(COLOR, 14));
    }

    public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext ctx) {
        return AABBs.get(state.get(FACING));
    }

    public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
        ItemStack itemStack = player.getStackInHand(hand);

        if (world.isClient) {
            return (VanillaCompat.COLOR_ITEMS.containsKey(itemStack.getItem())) ? ActionResult.SUCCESS : ActionResult.PASS;
        }
        
        if (VanillaCompat.COLOR_ITEMS.containsKey(itemStack.getItem())) {
            world.setBlockState(pos, state.with(COLOR, VanillaCompat.COLOR_ITEMS.get(itemStack.getItem())), 3);
            return ActionResult.CONSUME;
        }
        return ActionResult.PASS;
    }

    public BlockState getPlacementState(ItemPlacementContext ctx) {
        World blockView = ctx.getWorld();
        BlockPos blockPos = ctx.getBlockPos();
        HorConnectionDir config = ctx.getWorld().getGameRules().get(NekoConfig.HOR_CONNECTION_DIR).get();
        boolean useLeft = config == HorConnectionDir.LEFT2RIGHT || config == HorConnectionDir.BOTH;
        
        if (config != HorConnectionDir.NEITHER){
            BlockPos blockPosRef = (useLeft) ?
                getLeftBlock(blockPos, ctx.getPlayerFacing().getOpposite()) :
                getRightBlock(blockPos, ctx.getPlayerFacing().getOpposite());
            BlockState stateRef = blockView.getBlockState(blockPosRef);

            boolean connect = stateRef.getBlock() instanceof DyeableHorizontalConnectBlock && (connectOtherVariant || stateRef.getBlock() == this);

            if (!connect && config == HorConnectionDir.BOTH){ // Block on the left refuses to connect, try the right one
                blockPosRef = getRightBlock(blockPos, ctx.getPlayerFacing().getOpposite());
                stateRef = blockView.getBlockState(blockPosRef);
                connect = stateRef.getBlock() instanceof DyeableHorizontalConnectBlock && (connectOtherVariant || stateRef.getBlock() == this);
                useLeft = false;
            }
            if (connect) {
                if (useLeft){
                    switch (stateRef.get(CONNECTION)) {
                        case S0:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, HorizontalConnection.D1);
                        case D0:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, HorizontalConnection.D1);
                        case T0:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, HorizontalConnection.D1);
                        case D1:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, type == ConnectionType.DOUBLE ? HorizontalConnection.S0 : HorizontalConnection.T2);
                        case T1:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, HorizontalConnection.T2);
                        case T2:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, type == ConnectionType.BEAM ? HorizontalConnection.T2 : HorizontalConnection.S0);
                        default:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, HorizontalConnection.T2);
                        }
                } else {
                    switch (stateRef.get(CONNECTION)) {
                        case S0:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, HorizontalConnection.D0);
                        case D1:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, HorizontalConnection.D0);
                        case T2:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, HorizontalConnection.D0);
                        case D0:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, type == ConnectionType.DOUBLE ? HorizontalConnection.S0 : HorizontalConnection.T0);
                        case T1:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, HorizontalConnection.T0);
                        case T0:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, type == ConnectionType.BEAM ? HorizontalConnection.T0 : HorizontalConnection.S0);
                        default:
                            return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, HorizontalConnection.T0);
                    }
                }
            }
        }
        return super.getPlacementState(ctx).with(FACING, ctx.getPlayerFacing().getOpposite()).with(CONNECTION, HorizontalConnection.S0);
    }

    public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState newState, WorldAccess world, BlockPos pos, BlockPos posFrom) {
        BlockState res = state;
        // TODO HOW THE HELL TO DO THIS
        HorConnectionDir config = (world.getServer() == null) ? HorConnectionDir.LEFT2RIGHT : world.getServer().getGameRules().get(NekoConfig.HOR_CONNECTION_DIR).get();
        boolean flag1 = direction == getRightDir(state.get(FACING)) && (config == HorConnectionDir.LEFT2RIGHT ||  config == HorConnectionDir.BOTH);
        boolean flag2 = direction == getLeftDir(state.get(FACING)) && (config == HorConnectionDir.RIGHT2LEFT ||  config == HorConnectionDir.BOTH);

        boolean connect = flag1 || flag2;
        if (connect && newState.getBlock() instanceof DyeableHorizontalConnectBlock && (connectOtherVariant || newState.getBlock() == this)) {
            BlockState stateRef;
            if (flag1){ // Block on the right is ...
                stateRef = world.getBlockState(getLeftBlock(pos, state.get(FACING))); // Take the block on the left as an extra reference
                switch (newState.get(CONNECTION)) {
                    case D1:
                        return res.with(CONNECTION, HorizontalConnection.D0);
                    case T1:
                        return res.with(CONNECTION, (type == ConnectionType.BEAM && stateRef.getBlock() instanceof DyeableHorizontalConnectBlock && (connectOtherVariant || stateRef.getBlock() == this)) ? HorizontalConnection.T1 : HorizontalConnection.T0);
                    case T2:
                        return res.with(CONNECTION, HorizontalConnection.T1);
                    default:
                        break;
                }
            } else { // Block on the left is ...
                stateRef = world.getBlockState(getRightBlock(pos, state.get(FACING))); // Take the block on the right as an extra reference
                switch (newState.get(CONNECTION)) {
                    case D0:
                        return res.with(CONNECTION, HorizontalConnection.D1);
                    case T1:
                        return res.with(CONNECTION, (type == ConnectionType.BEAM && stateRef.getBlock() instanceof DyeableHorizontalConnectBlock && (connectOtherVariant || stateRef.getBlock() == this)) ? HorizontalConnection.T1 : HorizontalConnection.T2);
                    case T0:
                        return res.with(CONNECTION, HorizontalConnection.T1);
                    default:
                        break;
                }
            }
        }
        return res;
    }
}